import * as d3 from 'd3'

class AGV2DMap {
  /**
   * AGV监控的2D地图可视化对象
   * @param {DOM} container -dom对象的名称 id或者class
   * @param {Object} mapData -地图数据，包括AGV，路径，现场设备的图形信息
   */
  constructor(container, mapData) {
    // 加载地图数据
    this._mapDataLoader(mapData);
    let width = d3.select(container).node().getBoundingClientRect().width;
    let height = d3.select(container).node().getBoundingClientRect().height;
    // let width = height / this.mapHeight * this.mapWidth;
    let padding = { top: 60, right: 30, bottom: 30, left: 30 };

    // 添加SVG画布
    this.svg = d3.select(container).append('svg')
    // .attr('viewBox', [0, 0, width, height]);
      .style('width', width)
      .style('height', height);
    
    // 添加固定设备层
    this.equipmentLayer = this.svg.append('g').attr('id', 'equipment');
    // 添加路径层
    this.agvPathsLayer = this.svg.append('g').attr('id', 'agv-paths');
    // 添加站点层
    this.agvStationstLayer = this.svg.append('g').attr('id', 'agv-stations');
    // 添加AGV显示层
    this.agvCarLayer = this.svg.append('g').attr('id', 'agv-car');
    // 添加数据框层
    this.infoLayer = this.svg.append('g').attr('id', 'agv-info');

    // 生成y坐标轴
    this.yScale = d3.scaleLinear()
      .domain([0, this.mapHeight])
      .range([height - padding.bottom, padding.top]);
    this.svg.append('g')
      .attr('transform', `translate(${padding.left}, 0)`)
      .attr('stroke', 'white')
      .call(d3.axisLeft(this.yScale).ticks(this.mapHeight / 5))
      .call(g => g.selectAll('path')
        .attr('stroke', 'white'))
      .call(g => g.selectAll('.tick line')
        .attr('stroke', 'white'));

    
    // 生成x坐标轴
    this.xScale = d3.scaleLinear()
      .domain([0, this.mapWidth])
      .range([padding.left, width - padding.right]);
    this.svg.append('g')
      .attr('transform', `translate(0, ${height - padding.bottom})`)
      .attr('stroke', 'white')
      .call(d3.axisBottom(this.xScale).ticks(this.mapWidth / 5))
      .call(g => g.selectAll('path')
        .attr('stroke', 'white'))
      .call(g => g.selectAll('.tick line')
        .attr('stroke', 'white'));

    this._addStations();
    this._addPaths();
    this._addEquipments();
    this._addAGVs();
  }
  /**
   * 加载地图数据到svg中
   * @param {Object} mapData -地图相关数据
   */
  _mapDataLoader(mapData) {
    this.mapName = mapData.mapName;
    this.mapHeight = mapData.height;
    this.mapWidth = mapData.width;
    this.stations = mapData.stations;
    this.paths = mapData.paths;
    this.guides = mapData.guides;
    this.agvs = mapData.agvs;
    this.equipments = mapData.equipments;
  }
  /**
   * 将所有有效站点信息映射到地图上显示
   */
  _addStations() {
    const boderWidth = 150;
    const boderHeight = 150;
    this.stations.forEach(s => {
      let mapX = this.xScale(s.point.x);
      let mapY = this.yScale(s.point.y);
      let _cir = this.agvStationstLayer.append('g')
        .attr('id', `${s.id}`)
        .call(g => {
          g.append('circle')
            .attr('r', 6)
            .attr('cx', mapX)
            .attr('cy', mapY)
            .attr('stroke', '#eee')
            .attr('stroke-width', 2)
        })
      // 如果对象为普通支点，则显示为灰色并且不显示信息框
      if(s.name === undefined) {
        _cir.attr('fill', '#aaa');
        return;
      }
      _cir.attr('fill', s.status);
      this.infoLayer.append('g')
        .call(g => {
          g.append('image')
            .attr('x', mapX - boderWidth / 2)
            .attr('y', mapY - boderHeight + 20)
            .attr('height', boderHeight)
            .attr('width', boderWidth)
            .attr('xlink:href', require('../imgs/info_border.png'))
        })
        .call(g => {
          let text = g.append('text');
          text.attr('fill', 'white')
            .text(s.name)
            .attr('font-weight', 900)
            .attr('font-size', 16)
            .attr('stroke', '#7987ef')
            .attr('x', mapX - text.node().getBoundingClientRect().width / 2)
            .attr('y', mapY - boderHeight + 85);
        })
        .call(g => {
          let text = g.append('text');
          text.attr('fill', s.status)
            .text('状态: ' + s.info)
            .attr('font-weight', 700)
            .attr('font-size', 12)
            .attr('x', mapX - text.node().getBoundingClientRect().width / 2)
            .attr('y', mapY - boderHeight + 105);
        })
        .call(g => {
          let text = g.append('text');
          text.attr('fill', '#69ff8a')
            .text('产量: ' + s.product)
            .attr('font-weight', 700)
            .attr('font-size', 14)
            .attr('x', mapX - text.node().getBoundingClientRect().width / 2)
            .attr('y', mapY - boderHeight + 125);
        })
    });
  }
  /**
   * 根据站点和导点信息描绘出所有路径
   */
  _addPaths() {
    this.paths.forEach(path => {
      let pathGenerator = d3.line().curve(d3[path.type]);
      let fwPoints = [];
      let x = this.xScale(this.stations[path.stations[0]].point.x);
      let y = this.yScale(this.stations[path.stations[0]].point.y);
      fwPoints.push([x, y]);
      path.guides.forEach(p => {
        x = this.xScale(this.guides[p].x);
        y = this.yScale(this.guides[p].y);
        fwPoints.push([x, y]);
      });
      x = this.xScale(this.stations[path.stations[1]].point.x);
      y = this.yScale(this.stations[path.stations[1]].point.y);
      fwPoints.push([x, y]);

      // 加载正向路径
      path.fwd = pathGenerator(fwPoints);
      // 加载反向路径
      let bwPoints = [];
      for(let i = 0; i < fwPoints.length; i++) {
        bwPoints[i] = fwPoints[fwPoints.length - 1 - i];
      }
      path.bwd = pathGenerator(bwPoints);
    });

    this.paths.forEach(path => {
      this.agvPathsLayer.append('path')
        .attr('d', path.fwd)
        .attr('fill', 'none')
        .attr('stroke-width', 3)
        .attr('stroke', 'white');
    })
  }
  /**
   * 添加AGV设备到地图中
   */
  _addAGVs() {
    this.agvs.forEach(agv => {
      let _agv = this.agvCarLayer.append('g')
        .attr('id', agv.id)
        .call(g => {
          g.append('circle')
            .attr('fill', agv.color)
            .attr('r', 10)
            .attr('cx', 10)
            .attr('cy', 0)
        })
        .call(g => {
          g.append('rect')
            .attr('fill', agv.color)
            .attr('width', 20)
            .attr('height', 20)
            .attr('x', -10)
            .attr('y', -10)
        })
        .call(g => {
          let text = g.append('text');
          text.text(agv.id)
            .attr('fill', 'white')
            .attr('x', - text.node().getBoundingClientRect().width / 2 + 10)
            .attr('y', -20)
        })
      
      agv.nowPath = [];
      /**
       * AGV 运动动画
       * @param {首端站点id} startID 
       * @param {末端站点id} endID 
       * @param {是否启用动画} isMove 
       */
      agv.moving = (startID, endID, isMove) => {
        // 判断当前动画是否执行
        if(isMove !== undefined) {
          if(!isMove) {
            _agv.style('animation-play-state', 'paused');
          } else {
            _agv.style('animation-play-state', 'running');
          }
        }
        // 利用目标路径起点和终点的ID查找具体路径
        let sp = this.stations.map(s => s.id).indexOf(startID);
        let ep = this.stations.map(s => s.id).indexOf(endID);
        // 判断是否为当前正在执行的路径动画，如果是则直接返回不重新渲染
        if(agv.nowPath[0] === sp && agv.nowPath[1] === ep) {
          return;
        } else {
          agv.nowPath[0] = sp;
          agv.nowPath[1] = ep;
        }

        let forward = true; // 判断路径的方向是否为正向
        let targetPath = this.paths.find(path => {
          return (path.stations[0] === sp && path.stations[1] === ep);
        });
        if(targetPath === undefined) {
          forward = false;
          targetPath = this.paths.find(path => {
            return (path.stations[1] === sp && path.stations[0] === ep);
          });
        }
        // 当路径不存在时则返回，不进行后续动画处理
        if(targetPath === undefined) return;
        // 利用css样式设置两种动画，欺骗浏览器执行
        if(_agv.style('animation-name') !== 'svg-path-animation0') {
          _agv.style('animation-name', 'svg-path-animation0');
        } else {
          _agv.style('animation-name', 'svg-path-animation1');
        }
        if(forward) {
          _agv.style('offset-path', `path("${targetPath.fwd}")`);
        } else {
          _agv.style('offset-path', `path("${targetPath.bwd}")`);
        }
        _agv.style('animation-duration', `${targetPath.time}s`);
      }
    })
  }
  /**
   * 添加产线设备到地图中
   */
  _addEquipments() {
    this.equipments.forEach(q => {
      let mapX = this.xScale(q.point.x);
      let mapY = this.yScale(q.point.y);
      let width = Math.abs(this.xScale(q.point.x + q.width) - mapX);
      let height = Math.abs(this.yScale(q.point.y + q.height) - mapY);

      this.equipmentLayer.append('g')
        .attr('id', `${q.id}`)
        .call(g => {
          g.append(q.shape)
            .attr('x', mapX)
            .attr('y', mapY)
            .attr('width', width)
            .attr('height', height)
            .attr('fill', q.color)
        })
        .call(g => {
          let text = g.append('text');
            text.attr('fill', 'white')
            .text(q.name)
            .style('writing-mode', () => {
              if(q.width < q.height) return 'tb';
              else return 'lr';
            })
            .attr('font-size', 18)
            .attr('font-weight', 900)
            .attr('stroke', '#4f4faf')
            .attr('x', mapX + width / 2)
            .attr('y', mapY + (height - text.node().getBoundingClientRect().height) / 2);
        })
    })
  }
}

export {
  AGV2DMap
}